package com.dog.dao.impl;

import com.dog.dao.DAO;
import com.dog.model.BaseEntity;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.hibernate.*;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import java.io.Serializable;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;

/**
 * User: Dmitriy Akishin
 * Date: 08.03.2010
 * Time: 14:05:12
 */
public class DAOImpl<T extends BaseEntity> extends HibernateDaoSupport implements DAO<T> {

    private Class clazz;
    private Logger logger = Logger.getLogger(DAOImpl.class);

    public DAOImpl(Class clazz) {
        this.clazz = clazz;
    }

    public T findById(Long id) {
        T t = null;
        try {
            t = (T) getHibernateTemplate().get(clazz, id);
        } catch (HibernateException exception) {
            logger.log(Level.WARN, exception);
        }

        return t;
    }

    public T save(T entity) {
        try {
            getHibernateTemplate().save(entity);
        } catch (HibernateException exception) {
            logger.log(Level.WARN, exception);
        }

        return entity;
    }

    public void update(T entity) {

        try {
            getHibernateTemplate().update(entity);
        } catch (HibernateException exception) {
            logger.log(Level.WARN, exception);
        }

        return;

    }

    public void delete(T entity) {

        try {
            getHibernateTemplate().delete(entity);
        } catch (HibernateException exception) {
            logger.log(Level.WARN, exception);
        }

        return;

    }

    @Override
    public int getTotalCount() {
        return (Integer) getHibernateTemplate().execute(new HibernateCallback() {

            @Override
            public Object doInHibernate(Session session) throws HibernateException, SQLException {
                return session.createCriteria(clazz).setProjection(Projections.rowCount()).uniqueResult();
            }
        });

    }

    @Override
    public List<T> getByRange(final int firstResult, final int maxResult, final String field, final boolean asc) {

        return getHibernateTemplate().executeFind(new HibernateCallback<Object>() {
            @Override
            public Object doInHibernate(Session session) throws HibernateException, SQLException {
                Criteria criteria = session.createCriteria(clazz).setFirstResult(firstResult).setMaxResults(maxResult);
                if (field == null) return criteria.list();
                criteria.addOrder(asc ? Order.asc(field) : Order.desc(field));
                return criteria.list();
            }
        });

    }

    @Override
    public List<T> list() {

        return getHibernateTemplate().executeFind(new HibernateCallback<Object>() {

            @Override
            public Object doInHibernate(Session session) throws HibernateException, SQLException {
                return session.createCriteria(clazz).list();
            }
        });

    }

    @Override
    public List<T> find(String queryString, Object value) {
        return getHibernateTemplate().find(queryString, value);
    }


    public int bulkUpdate(String queryString, Object[] values) {
        return getHibernateTemplate().bulkUpdate(queryString, values);
    }

    public List<T> findAll() {
        DetachedCriteria criteria = DetachedCriteria.forEntityName(clazz.getName());
        return getHibernateTemplate().findByCriteria(criteria);
    }

    public T findById(final Serializable id) {
        return (T) getHibernateTemplate().get(clazz.getName(), id);
    }

    public List<T> findByExample(T example) {
        return (List<T>) getHibernateTemplate().findByExample(example);
    }

    public void evict(final T o) {
        getHibernateTemplate().evict(o);
    }


    public List<T> find(String query, Object... values) {
        return getHibernateTemplate().find(query, values);
    }

    public List<T> findByNamedParameters(String queryString, String[] paramNames, Object[] values) {
        return getHibernateTemplate().findByNamedParam(queryString, paramNames, values);
    }


    public Number findScalar(final String sql, final Object... values) {
        return (Number) getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
            public Object doInHibernate(org.hibernate.Session session) throws HibernateException {
                Query queryObject = session.createQuery(sql);
                if (values != null) {
                    for (int i = 0; i < values.length; i++) {
                        queryObject.setParameter(i, values[i]);
                    }
                }
                Iterator<Double> it = queryObject.iterate();
                return it.next();
            }
        });

    }


    public List<String> findStringList(final String sql, final Object... values) {
        return (List<String>) getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
            public Object doInHibernate(org.hibernate.Session session) throws HibernateException {
                Query queryObject = session.createQuery(sql);
                if (values != null) {
                    for (int i = 0; i < values.length; i++) {
                        queryObject.setParameter(i, values[i]);
                    }
                }

                return queryObject.list();
            }
        });
    }


    public List<T> findByNamedParameters(final String sql, final String[] paramNames, final Object[] values, final int maxResult) {
        return (List<T>) getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
            public Object doInHibernate(org.hibernate.Session session) throws HibernateException {
                Query queryObject = session.createQuery(sql).setMaxResults(maxResult);
                setParameters(queryObject, values, paramNames);
                return queryObject.list();
            }
        });


    }

    public List<T> findByNamedParameters(final String sql, final List<String> paramNames, final List<Object> values, final int maxResult) {
        return (List<T>) getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
            public Object doInHibernate(org.hibernate.Session session) throws HibernateException {
                Query queryObject = session.createQuery(sql).setMaxResults(maxResult);
                setParameters(queryObject, values, paramNames);
                return queryObject.list();
            }
        });


    }

    private void setParameters(Query queryObject, List values, List<String> paramNames) {
        int i = 0;
        if (values != null) {
            for (Object value : values) {
                queryObject.setParameter(paramNames.get(i), value);
                i++;
            }

        }


    }


    private void setParameters(Query queryObject, Object[] values, String[] paramNames) {
        if (values != null) {
            for (int i = 0; i < values.length; i++) {
                queryObject.setParameter(paramNames[i], values[i]);
            }
        }
    }

    public List<T> findByNamedParameter(String queryString, String paramName, Object value) {
        return getHibernateTemplate().findByNamedParam(queryString, paramName, value);
    }

    public T findUniqueByNamedParameters(String queryString, String[] paramNames, Object[] values) {
        return getUnique(getHibernateTemplate().findByNamedParam(queryString, paramNames, values));
    }

    public T findUniqueByNamedParameter(String queryString, String paramName, Object value) {
        return getUnique(getHibernateTemplate().findByNamedParam(queryString, paramName, value));
    }


    public List<T> findByNamedQuery(String queryName, Object... values) {
        return getHibernateTemplate().findByNamedQuery(queryName, values);
    }

    public T findUnique(String query, Object... values) {
        List<T> results = getHibernateTemplate().find(query, values);
        return getUnique(results);
    }

    public T findUniqueByNamedQuery(String queryName, Object... values) {
        List<T> results = getHibernateTemplate().findByNamedQuery(queryName, values);
        return getUnique(results);
    }

    protected T getUnique(List<T> results) {
        if (results == null || results.size() == 0) {
            return null;
        }
        if (results.size() > 1) {
            throw new RuntimeException("getUnique fetches more then one result");
        }
        return results.get(0);
    }


    public void remove(final Serializable id) {
        getHibernateTemplate().delete(findById(id));
    }

    public List<T> findAllByFields(final String[] names, final Object[] values) {
        DetachedCriteria q = DetachedCriteria.forEntityName(clazz.getName());
        for (int i = 0; i < names.length; i++) {
            q.add(Restrictions.eq(names[i], values[i]));
        }

        return getHibernateTemplate().findByCriteria(q);
    }

    public T findUniqueByFields(final String[] names, final Object[] values) {
        return getUnique(findAllByFields(names, values));
    }

    public T findByFieldScalar(final String name, final Object value) {
        return getUnique(findByFieldScalarNonUnique(name, value));
    }

    public T findByFieldScalarLike(String names, Object value) {
        return getUnique(findByFieldScalarNonUniqueLike(names, value));
    }

    public List<T> findByFieldScalarNonUnique(final String name, final Object value) {
        DetachedCriteria q = DetachedCriteria.forEntityName(clazz.getName());
        q.add(Restrictions.eq(name, value));
        List<T> list = getHibernateTemplate().findByCriteria(q);
        if (list == null || list.size() == 0) {
            return null;
        }
        return list;
    }

    public List<T> findByFieldScalarNonUniqueLike(final String name, final Object value) {
        DetachedCriteria q = DetachedCriteria.forEntityName(clazz.getName());
        q.add(Restrictions.like(name, value));
        List<T> list = getHibernateTemplate().findByCriteria(q);
        if (list == null || list.size() == 0) {
            return null;
        }
        return list;
    }


    public List finddd(final String queryString, final Object[] values) throws DataAccessException {
        return (List) getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
            public Object doInHibernate(org.hibernate.Session session) throws HibernateException {
                Query queryObject = session.createQuery(queryString);
                if (values != null) {
                    for (int i = 0; i < values.length; i++) {
                        queryObject.setParameter(i, values[i]);
                    }
                }
                return queryObject.list();
            }
        });
    }


    public List<T> find(final String sql, final int maxResultCount, final int firstResultIndex) {
        return (List<T>) getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
            public Object doInHibernate(org.hibernate.Session session) throws HibernateException {
                Query queryObject = session.createQuery(sql)
                        .setMaxResults(maxResultCount)
                        .setFirstResult(firstResultIndex);

                return queryObject.list();
            }
        });
    }

    public List<T> findWithListParam(final String sql, final String[] paramNames, final Object[] params, final List<Long> listParam) {
        return (List<T>) getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
            public Object doInHibernate(org.hibernate.Session session) throws HibernateException {
                Query queryObject = session.createQuery(sql);
                setParameters(queryObject, params, paramNames, listParam);
                return queryObject.list();
            }
        });
    }

    public T findUniqueWithListParam(final String sql, final String[] paramNames, final Object[] params, final List<Long> listParam) {
        return (T) getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
            public Object doInHibernate(org.hibernate.Session session) throws HibernateException {
                Query queryObject = session.createQuery(sql);
                setParameters(queryObject, params, paramNames, listParam);
                return getUnique(queryObject.list());
            }
        });
    }


    private void setParameters(Query queryObject, Object[] params, String[] paramNames, List<Long> listParam) {
        int i = 0;
        if (params != null) {
            for (; i < params.length; i++) {
                queryObject.setParameter(paramNames[i], params[i]);
            }
        }
        if (listParam != null && !listParam.isEmpty()) {
            queryObject.setParameterList(paramNames[i], listParam);
        }

    }

    public void executeUpdate(final String sql, final String[] paramNames, final Object[] params, final List<Long> listParam) {
        getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
            public Object doInHibernate(org.hibernate.Session session) throws HibernateException {
                Query queryObject = session.createQuery(sql);
                setParameters(queryObject, params, paramNames, listParam);
                return queryObject.executeUpdate();
            }
        });

    }


    public List<T> findByCriteria(DetachedCriteria criteria) {
        return getHibernateTemplate().findByCriteria(criteria);
    }

    public List<T> findByCriteria(DetachedCriteria criteria, int firstResult, int maxResults) {
        return getHibernateTemplate().findByCriteria(criteria, firstResult, maxResults);
    }


    /**
     * Initializes proxy object. This method is usefull because we have to give to the web-tier
     * a fully loaded object with properties loaded which are marked as lazy.
     *
     * @param proxyObject a proxy that needs to be initialized (loaded).
     */
    public void initializeObjects(final Object proxyObject) {
        Hibernate.initialize(proxyObject);
    }

    protected Object execute(HibernateCallback action) {
        return getHibernateTemplate().execute(action);
    }

    protected List<T> findByQueryWithListParam(List paramList, String query, String paramName) {
        return getHibernateTemplate().getSessionFactory().getCurrentSession().createQuery(query).setParameterList(paramName, paramList).list();
    }

    public void lock(Object entity, LockMode lockMode) {
        getHibernateTemplate().lock(entity, lockMode);
    }

}
